/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Hardware.h"
#include "Channel.h"
#include <math/utl.h>
#include <logs/Log.h>

#include <boost/bind.hpp>

namespace liba {
namespace noise {
namespace hard {
namespace directx8 {

class VolumeConstraint
	: public properties::Constraint<double>
{
public:
	virtual double Process(double value) LIBA_OVERRIDE
	{
		return math::clamp(value, 0.0, 1.0);
	}
};

Hardware::Hardware(IDirectSound8 * lpds, IDirectSoundBuffer * primary_buffer):
	lpds( lpds ),
	listener( 0 ),
	prop( 0 ),
	exactVolume(1.0),
	listenerFront(math::Vector3<float>(0, 0, 1)),
	listenerUp(math::Vector3<float>(0, 1, 0)),
	max_playing_channels( 12 ),
	primary_buffer( primary_buffer )
{
	this->listenerPosition.Changed().connect(boost::bind(&Hardware::ListenerPositionChanged, this, _1));
	this->RegisterProperty(listener_position_name, &this->listenerPosition);
	this->listenerSpeed.Changed().connect(boost::bind(&Hardware::ListenerSpeedChanged, this, _1));
	this->RegisterProperty(listener_speed_name, &this->listenerSpeed);
	this->RegisterProperty(listener_front_name, &this->listenerFront);
	this->RegisterProperty(listener_up_name, &this->listenerUp);
	this->exactVolume.AddConstraint(std::auto_ptr<properties::Constraint<double> >(new VolumeConstraint()));
	this->exactVolume.Changed().connect(boost::bind(&Hardware::ExactVolumeChanged, this, _1));
	this->RegisterProperty(global_volume_name, &this->exactVolume);

	caps.dwSize = sizeof(caps);
	HRESULT hr = lpds->GetCaps( &caps );
	hr = primary_buffer->Play( 0, 0, DSBPLAY_LOOPING );
	hr = primary_buffer->QueryInterface( IID_IDirectSound3DListener, (void**)&listener);
	hr = primary_buffer->QueryInterface(IID_IKsPropertySet, (void**)&prop);
}

Hardware::~Hardware()
{
	for( ChannelStaticList::iterator it = static_channels.begin(); it != static_channels.end(); ++it )
	{
		(*it)->on_hardware_delete();
	}
	for( ChannelStreamingList::iterator it = streaming_channels.begin(); it != streaming_channels.end(); ++it )
	{
		(*it)->on_hardware_delete();
	}

	primary_buffer->Stop();
	primary_buffer->Release(); primary_buffer = 0;
	lpds->Release(); lpds = 0;
}

hard::Channel * Hardware::create_static_channel( const Atom & channel_type, const filesystem::String & name)
{
	return new ChannelStatic(this, channel_type, name );
}
hard::Channel * Hardware::create_streaming_channel( const Atom & channel_type )
{
	return new ChannelStreaming(this, channel_type, 5 );
}

void Hardware::life_cycle(double delta_time)
{
	ChannelStaticList::iterator it = playing_channels.begin();
	while( it != playing_channels.end() )
	{
		(*it)->life_cycle(delta_time, listenerPosition);
		if( (*it)->is_finished() )
			it = playing_channels.erase( it );
		else
			++it;
	}

///	logs::log() << "Hardware::life_cycle - " << playing_channels.size() << " channels\n";

	playing_channels.sort( ChannelStatic::SortPredicate );
///	std::sort(.begin(), playing_channels.end(), ChannelStatic::SortPredicate );

	it = playing_channels.begin();
	for( int counter = 0; it != playing_channels.end(); ++counter, ++it )
	{
		if( counter < max_playing_channels && !(*it)->in_existance() )
		{
			(*it)->come_to_existance();
		}
		if( counter >= max_playing_channels && (*it)->in_existance() )
		{
			(*it)->begone();
		}
	}

	{
		ChannelStreamingList::iterator it = streaming_channels.begin();
		for( ; it != streaming_channels.end(); ++it )
			(*it)->life_cycle(delta_time);
	}

	listener->SetOrientation( listenerFront.Get().x, listenerFront.Get().y, listenerFront.Get().z, 
		listenerUp.Get().x, listenerUp.Get().y, listenerUp.Get().z, DS3D_DEFERRED );
	listener->CommitDeferredSettings();
}

void Hardware::ListenerPositionChanged(const math::Vector3<float>& value)
{
	HRESULT hr = 0;
	if( listener )
		hr = listener->SetPosition( value[0], value[1], value[2], DS3D_DEFERRED );
}

void Hardware::ListenerSpeedChanged(const math::Vector3<float>& value)
{
	HRESULT hr = 0;
	if( listener )
		hr = listener->SetVelocity( value[0], value[1], value[2], DS3D_DEFERRED );
}

void Hardware::ExactVolumeChanged(double value)
{
	primary_buffer->SetVolume( convert_linear_volume(value) );
}

int Hardware::convert_linear_volume(double value)
{
	double bell = log10( value );
	bell = math::clamp<double>( bell, -10, 0 );

	double part = (bell + 10)/10;

	return int(DSBVOLUME_MIN + (DSBVOLUME_MAX - DSBVOLUME_MIN) * part);
}

} // namespace directx8
} // namespace hard
} // namespace noise
} // namesapce liba

